#include "../core/crt.h"
#include <math.h>
#include "../dxut/dxstdafx.h"
#include "Framework.h"


#ifndef _DEBUG
#define TEST_PERFORMANCE	1
#else
#define TEST_PERFORMANCE	0
#endif

#if TEST_PERFORMANCE
#define CRT_OUTPUT_WIDTH	640
#define CRT_OUTPUT_HEIGHT	480
#else
#define CRT_OUTPUT_WIDTH	160
#define CRT_OUTPUT_HEIGHT	120
#endif



HWND	CRTDialog;
int		CRTDialogWidth	= CRT_OUTPUT_WIDTH;
int		CRTDialogHeight = CRT_OUTPUT_HEIGHT;

CRT			g_crt;
TCRTScene	g_scene;
TCRTOutput	g_output;

extern CFirstPersonCamera      g_Camera;                // A model viewing camera

HANDLE WorkingThread;
bool CRTInProgress;

// save the ray traced image as bitmap file
bool SaveCRTResult( TCHAR* filename )
{
	if (g_output.buffer == NULL || filename == NULL)
		return false;

	FILE *fp = _wfopen( filename, L"wb" );
	if (!fp)
		return false;

	BITMAPFILEHEADER	bfh;
	ZeroMemory( &bfh, sizeof( BITMAPFILEHEADER ) );
	bfh.bfType	= 0x4d42;	// "BM"
	bfh.bfSize	= sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFOHEADER ) + g_output.imageh * g_output.imagew * 3;
	bfh.bfOffBits = sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFOHEADER );

	BITMAPINFOHEADER	bih;
	ZeroMemory( &bih, sizeof( BITMAPINFOHEADER ) );
	bih.biSize		= sizeof( BITMAPINFOHEADER );
	bih.biWidth		= g_output.imagew;
	bih.biHeight	= g_output.imageh;
	bih.biPlanes	= 1;
	bih.biBitCount	= 24;
	bih.biCompression	= BI_RGB;

	if (fwrite( &bfh, sizeof( BITMAPFILEHEADER ), 1, fp ) != 1)
	{
		fclose( fp );
		return false;
	}
	
	if (fwrite( &bih, sizeof( BITMAPINFOHEADER ), 1, fp ) != 1)
	{
		fclose( fp );
		return false;
	}

	if (fwrite( g_output.buffer, g_output.imageh * g_output.imagew * 3, 1, fp ) != 1)
	{
		fclose( fp );
		return false;
	}

	fclose( fp );
	return true;
}

//// data to be passed to the working thread
//struct TWorkingThreadInput
//{
//	ThreadStartFunc		WorkStart;
//	ThreadStopFunc		WorkStop;
//	ThreadProgressFunc	WorkProgress;
//};

// Progress callback function for the ray tracer
void CALLBACK OnCRTStart()
{
	CRTInProgress = true;
}

void CALLBACK OnCRTStop()
{
	CRTInProgress = false;
}

void CALLBACK OnCRTProgress(float progress, void *userdata)
{
	g_output = g_crt.GetOutput();

	TCHAR msg[256];
	//float Value = *(reinterpret_cast<float*>(userdata));

	StringCchPrintf(msg, 256, L"Iteration %02d%%", int(progress * 100));
	SetWindowText(CRTDialog, msg);



	// show the temp result on dialog DC
	HDC hdc = GetDC( CRTDialog );
	RECT rect;

	GetClientRect( CRTDialog, &rect );
	int nClientWidth = rect.right - rect.left + 1;
	int nClientHeight = rect.bottom - rect.top + 1;
	int nClientClusterWidth = (g_output.nDirtyRectWidth * nClientWidth + g_output.imagew - 1) / g_output.imagew;
	int nClientClusterHeight = (g_output.nDirtyRectHeight * nClientHeight + g_output.imageh - 1) / g_output.imageh;
	int nClientDirtyX = g_output.nDirtyRectX * nClientWidth / g_output.imagew;
	int nClientDirtyY = g_output.nDirtyRectY * nClientHeight / g_output.imageh;
	int nClientDirtyWidth = min(nClientWidth - nClientDirtyX, nClientClusterWidth);
	int nClientDirtyHeight = min(nClientHeight - nClientDirtyY, nClientClusterHeight);

	BITMAPINFO bi;
	memset(&bi, 0, sizeof(BITMAPINFO));
	bi.bmiHeader.biSize		= sizeof( BITMAPINFOHEADER );
	bi.bmiHeader.biWidth	= g_output.imagew;
	bi.bmiHeader.biHeight	= g_output.imageh;	
	bi.bmiHeader.biPlanes	= 1;
	bi.bmiHeader.biBitCount	= 24;
	bi.bmiHeader.biCompression = BI_RGB;

	SetStretchBltMode(hdc, HALFTONE);
	int result = StretchDIBits(
		hdc, 
		nClientDirtyX, 
		// Client originl starts at left-upper corner while Bitmap origin is at left-lower corner
		nClientHeight - 1 - nClientDirtyHeight - nClientDirtyY,		
		nClientDirtyWidth, 
		nClientDirtyHeight,
		g_output.nDirtyRectX, 
		g_output.nDirtyRectY, 
		g_output.nDirtyRectWidth,
		g_output.nDirtyRectHeight,
		g_output.buffer, 
		&bi, 
		DIB_RGB_COLORS,
		SRCCOPY );

	if (result == GDI_ERROR)
		ErrorMessageDialog( GetLastError() );

	ReleaseDC( CRTDialog, hdc );
}



// RT thread entry function
DWORD WINAPI WorkingThreadMain(PVOID Param)
{
	// set output
	g_crt.SetOutput(g_output);

	// set camera
	TCRTCamera camera;

	//const D3DXMATRIX* world = g_Camera.GetWorldMatrix();
	const D3DXMATRIX* view	= g_Camera.GetViewMatrix();
	const D3DXMATRIX* proj	= g_Camera.GetProjMatrix();

	//D3DXMATRIX  vp, invvp;
	////D3DXMatrixMultiply( &vp0, view, world );
	//D3DXMatrixMultiply( &vp, proj, view );
	//D3DXMatrixInverse( &invvp, NULL, &vp );

	for (int r = 0; r < 4; r++)
		for (int c = 0; c < 4; c++)
		{
			//camera.invVPmatrix.m[r * 4 + c] = invvp(r, c);
			camera.projM.m[r * 4 + c] = (*proj)(r, c);
			camera.viewM.m[r * 4 + c] = (*view)(r, c);
		}

	const D3DXVECTOR3* pos = g_Camera.GetEyePt();
	camera.position = VEC3(pos->x, pos->y, pos->z);


	//const D3DXVECTOR3* pos = g_Camera.GetEyePt();
	//camera.position = VEC3(pos->x, pos->y, pos->z);
	//const D3DXVECTOR3* lookat = g_Camera.GetLookAtPt();
	//camera.direction = VEC3(lookat->x - pos->x, lookat->y - pos->y, lookat->z - pos->z);
	//camera.direction.Normalize();

	//camera.perspective = true;
	//camera.fov		= g_Camera.GetFOV();
	//camera.aspect	= g_Camera.GetAspect();
	//camera.nearz	= g_Camera.GetNearClip();
	//camera.farz		= g_Camera.GetFarClip();

	g_crt.SetCamera(camera);

	// set scene
	g_crt.SetScene(&g_scene);

	int result = g_crt.Run();

	return result;
}

//TWorkingThreadInput gThreadInput;

void RayTraceScene()
{
	DWORD ThreadID; 

	//gThreadInput.WorkStart = OnThreadStart;
	//gThreadInput.WorkStop = OnThreadStop;
	//gThreadInput.WorkProgress = OnThreadProgress;

	WorkingThread = CreateThread(
		NULL,
		0, 
		(LPTHREAD_START_ROUTINE)WorkingThreadMain, 
		NULL,
		CREATE_SUSPENDED,	// resume the thread so that we can set its priority to highest
		&ThreadID
		);

	if (WorkingThread == NULL)
		ErrorMessageDialog( GetLastError() );

	SetThreadPriority( WorkingThread, THREAD_PRIORITY_HIGHEST/*ABOVE_NORMAL*/ );
	ResumeThread( WorkingThread );
}


void CRTDialogOnPaint(HWND hWnd)
{
	PAINTSTRUCT ps;

	BeginPaint( hWnd, &ps );

	// show the temp result on dialog DC
	HDC hdc = GetDC( CRTDialog );
	RECT rect;

	GetClientRect( CRTDialog, &rect );

	BITMAPINFO bi;
	memset(&bi, 0, sizeof(BITMAPINFO));
	bi.bmiHeader.biSize			= sizeof( BITMAPINFOHEADER );
	bi.bmiHeader.biWidth		= g_output.imagew;
	bi.bmiHeader.biHeight		= g_output.imageh;
	bi.bmiHeader.biPlanes		= 1;
	bi.bmiHeader.biBitCount		= 24;
	bi.bmiHeader.biCompression	= BI_RGB;

	SetStretchBltMode(hdc, HALFTONE);
	int result = StretchDIBits( hdc, 
		0, 0, 
		rect.right - rect.left + 1, 
		rect.bottom - rect.top + 1, 
		0, 0, 
		g_output.imagew,
		g_output.imageh,
		g_output.buffer, 
		&bi, 
		DIB_RGB_COLORS,
		SRCCOPY );

	if (result == GDI_ERROR)
		ErrorMessageDialog( GetLastError() );

	ReleaseDC( CRTDialog, hdc );

	EndPaint( hWnd, &ps );
}


// Process messages for the ray trace dialog
INT_PTR CALLBACK CRTDialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	RECT	rect;
	HDC		hDC;

	switch (message)
	{
	case WM_ERASEBKGND : 
		hDC = reinterpret_cast<HDC>(wParam);
		GetClientRect(CRTDialog, &rect);
		FillRect(hDC, &rect, CreateSolidBrush(RGB(0, 0, 0)));
		return 1;

	case WM_PAINT :
		CRTDialogOnPaint( hWnd );
		break;

	case WM_INITDIALOG :
		// set output
		g_output.imagew			= CRT_OUTPUT_WIDTH;
		g_output.imageh			= CRT_OUTPUT_HEIGHT;
		g_output.format			= CRT_OF_BGR;
		g_output.startfunc		= OnCRTStart;
		g_output.stopfunc		= OnCRTStop;
		g_output.progressfunc	= OnCRTProgress;
		g_output.buffer			= new char[g_output.imagew * g_output.imageh * 3];
		// Init color - gray
		memset(g_output.buffer, 64, g_output.imagew * g_output.imageh * 3);
		g_output.shadow			= true;
		break;

	case WM_DESTROY : 
		if (CRTInProgress)
			TerminateThread(WorkingThread, 0);
		SAFE_DELETE_ARRAY(g_output.buffer);
		break;

	default :
		break;
	}

	return 0;	
}

